package com.jz.dsq;

import com.jz.dsq.condition.Condition;
import com.jz.dsq.utils.Operator;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.*;
import java.util.function.Function;


/**
 * 条件构建接口
 *
 * @author zhangliang
 */
public abstract class ConditionBuilder<T> {

    public List<Condition> conditions = new ArrayList<>();

    private final Map<Operator, Function<Condition, Specification<T>>> specificationDispatcher = new HashMap<>();

    public void specificationDispatcherInit() {
        specificationDispatcher.put(Operator.eq, condition ->
                Specifications.<T>and().eq(condition.getKey(), condition.getValue()).build());
        specificationDispatcher.put(Operator.ne, condition ->
                Specifications.<T>and().ne(condition.getKey(), condition.getValue()).build());
        specificationDispatcher.put(Operator.like, condition ->
                Specifications.<T>and().like(condition.getKey(), (String) condition.getValue()).build());
        specificationDispatcher.put(Operator.notLike, condition ->
                Specifications.<T>and().notLike(condition.getKey(), (String) condition.getValue()).build());
        specificationDispatcher.put(Operator.ge, condition ->
                Specifications.<T>and().ge(condition.getKey(), (Comparable<Object>) condition.getValue()).build());
        specificationDispatcher.put(Operator.gt, condition ->
                Specifications.<T>and().gt(condition.getKey(), (Comparable<Object>) condition.getValue()).build());
        specificationDispatcher.put(Operator.le, condition ->
                Specifications.<T>and().le(condition.getKey(), (Comparable<Object>) condition.getValue()).build());
        specificationDispatcher.put(Operator.lt, condition ->
                Specifications.<T>and().lt(condition.getKey(), (Comparable<Object>) condition.getValue()).build());
        specificationDispatcher.put(Operator.in, condition ->
                Specifications.<T>and().in(condition.getKey(), (Collection<?>) condition.getValue()).build());
        specificationDispatcher.put(Operator.notIn, condition ->
                Specifications.<T>and().notIn(condition.getKey(), (Collection<?>) condition.getValue()).build());
        specificationDispatcher.put(Operator.between, condition ->
                Specifications.<T>and().between(condition.getKey(), ((Object[]) condition.getValue())[0], ((Object[]) condition.getValue())[1]).build());
    }

    public ConditionBuilder<T> cnd(String key, Operator operator, Object value) {
        Condition cnd = new Condition();
        cnd.setKey(key);
        cnd.setOperator(operator);
        cnd.setValue(value);
        conditions.add(cnd);
        return this;
    }

    public Specification<T> builder() {
        specificationDispatcherInit();
        List<Specification<T>> specifications = new ArrayList<>();
        conditions.forEach(condition -> {
            Function<Condition, Specification<T>> result = specificationDispatcher.get(condition.getOperator());
            specifications.add(result.apply(condition));
        });
        return (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            Predicate[] predicates = new Predicate[specifications.size()];
            for (int i = 0; i < specifications.size(); i++) {
                predicates[i] = specifications.get(i).toPredicate(root, query, cb);
            }
            if (Objects.equals(predicates.length, 0)) {
                return null;
            }
            return cb.and(predicates);
        };
    }
}
